

#include "NuXporter.h"
#pragma warning(disable: 4996)

#include "resource.h"

#define _WIN32_WINNT 0x501

#include <windows.h>
#include <commctrl.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9xof.h>
#include <atlbase.h>
#include <rmxftmpl.h>
#include <rmxfguid.h>
#include <d3dx9mesh.h>

#include <vector>
#include <set>
#include <string>
#include <algorithm>
#include <assert.h>

#include <psapi.h>

#include "DeleDAPI.h"

#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "comdlg32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "d3d9.lib")
#if !defined(NDEBUG)
 #pragma comment(lib, "d3dx9d.lib")
#else
 #pragma comment(lib, "d3dx9.lib")
#endif
#pragma comment(lib, "dxguid.lib")



HMODULE hModule;
HWND hDialog;
HHOOK hHook;
HWND hMainWindow;

struct Settings {
  std::string path;
  std::string effect;
  int format;
  bool normals;
  bool texcoords;
  bool mapcoords;
  bool materials;
  bool copyTextures;
  bool texturePaths;
  bool center;
  bool floorZero;
  bool identityRoot;
  bool mirrorZ;
  float scaleNumer;
  float scaleDenom;
};

static Settings settings = {
  std::string(""),
  std::string(""),
  0,
  true,
  true,
  false,
  true,
  true,
  false,
  true,
  false,
  false,
  1.f,
  100.f,
};
static bool settingsDirty = false;

std::string oldFiles[5];
std::string oldEffects[5];
std::string oldScales[5];

void UpdateArray(std::string * ary, size_t cnt, std::string const & str)
{
  for (size_t i = 0; i < cnt; ++i) {
    if (ary[i] == str) {
      std::swap(ary[0], ary[i]);
      return;
    }
  }
  std::copy_backward(ary, ary+cnt-1, ary+cnt);
  ary[0] = str;
}

void WriteArray(std::string * ary, size_t cnt, char const * name, FILE * f)
{
  for (size_t i = 0; i < cnt; ++i) {
    fprintf(f, "%s[%d]=%s\n", name, i, ary[i].c_str());
  }
}

void UpdateCombo(std::string const * ary, size_t count, DWORD idc)
{
  HWND combo = ::GetDlgItem(hDialog, idc);
  LRESULT lr = ::SendMessage(combo, CB_RESETCONTENT, 0, 0);
  lr = ::SendMessage(combo, CB_SETMINVISIBLE, count > 15 ? 15 : count, 0);
  for (size_t i = count; i > 0; --i) {
    char const * str = ary[i-1].c_str();
    if (str[0]) {
      lr = ::SendMessage(combo, CB_ADDSTRING, 0, (LPARAM)str);
    }
  }
}

template<typename T >
void VisitSettings(Settings & set, T & t)
{
  t.doString(IDC_FILEPATH, "path", set.path);
  t.doString(IDC_EFFECT, "effect", set.effect);
  t.doRatio(IDC_SCALE, "scale", set.scaleNumer, set.scaleDenom);
  static int radios[] = {
    IDC_TEXTFORMAT, IDC_BINARYFORMAT, 0,
  };
  t.doRadio(radios, "binary", set.format);
  t.doBool(IDC_NORMALS, "normals", set.normals);
  t.doBool(IDC_TEXCOORDS, "texcoords", set.texcoords);
  t.doBool(IDC_MAPCOORDS, "mapcoords", set.mapcoords);
  t.doBool(IDC_COPYTEXTURES, "copyTextures", set.copyTextures);
  t.doBool(IDC_TEXTUREPATHS, "texturePaths", set.texturePaths);
  t.doBool(IDC_MATERIALS, "materials", set.materials);
  t.doBool(IDC_CENTERMESH, "center", set.center);
  t.doBool(IDC_FLOORZERO, "floorZero", set.floorZero);
  t.doBool(IDC_IDENTITYROOT, "identityRoot", set.identityRoot);
  t.doBool(IDC_MIRRORZ, "mirrorZ", set.mirrorZ);
}

static void GetSettingsFilename(std::string & str)
{
  str = "c:\\NuXporter.ini";
  char name[512+32];
  if (::GetModuleFileName(::GetModuleHandle(0), name, 512) < 511) {
    char * s = strrchr(name, '\\');
    if (s) {
      strcpy(s+1, "Plugins\\NuXporter.ini");
      str = name;
    }
  }
}

static void GetTexturesFilename(std::string & str)
{
  str = "c:\\Textures";
  char name[512+32];
  if (::GetModuleFileName(::GetModuleHandle(0), name, 512) < 511) {
    char * s = strrchr(name, '\\');
    if (s) {
      strcpy(s+1, "Textures");
      str = name;
    }
  }
}

template<typename T> class vector2 : public std::vector<T> {
  public:
    void append(T const * src, size_t cnt) {
      size_t s = size();
      resize(s+cnt);
      std::copy(src, src+cnt, &(*this)[s]);
    }
};

static char legal_character(char ch)
{
  //  I'm pretty draconian on what I allow in object names
  if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
      (ch >= '0' && ch <= '9') || ch == '-') {
    return ch;
  }
  return '_';
}

static std::string convert_name(std::string i)
{
  std::string ret;
  ret.resize(i.size());
  std::transform(i.begin(), i.end(), ret.begin(), legal_character);
  return ret;
}

size_t gLocalProgressMax, gLocalProgress, gGlobalProgress, gGlobalProgressMax;

static void UpdateTask(std::string const & str, size_t localProgress)
{
  gLocalProgressMax = localProgress;
  gLocalProgress = 0;
  ::SetDlgItemText(hDialog, IDC_STATUSTEXT, str.c_str());
}

static void UpdateProgress(size_t n, size_t m)
{
  gGlobalProgress = n;
  gGlobalProgressMax = m;
  double a = 1000.0 * n / m;
  short p = (short)a;
  ::SendDlgItemMessage(hDialog, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, 1000));
  ::SendDlgItemMessage(hDialog, IDC_PROGRESS, PBM_SETPOS, p, 0);
}

static void LocalProgress(size_t n, size_t m)
{
  gLocalProgress = (size_t)((double)n / m * gLocalProgressMax);
  short p = (short)(1000.0 * (gGlobalProgress + gLocalProgress) / gGlobalProgressMax);
  ::SendDlgItemMessage(hDialog, IDC_PROGRESS, PBM_SETPOS, p, 0);
}

//todo: hack alert! The scene name is in the title of the main window...
static std::string GetSceneName()
{
  char str[512];
  ::GetWindowText(hMainWindow, str, sizeof(str));
  str[511] = 0;
  char * ptr = strstr(str, ".dmf");
  if (ptr == NULL) {
    HWND cur = hDialog;
    while (cur = ::GetWindow(cur, GW_HWNDNEXT)) {
      ::GetWindowText(cur, str, sizeof(str));
      str[511] = 0;
      ptr = strstr(str, ".dmf");
      if (ptr != NULL) {
        goto found_it;
      }
    }
    return "unnamed";
  }
found_it:
  *ptr = 0;
  while (ptr > str && ptr[-1] != ' ' && ptr[-1] != '\t' && ptr[-1] != '\\') {
    --ptr;
  }
  return ptr;
}


static void ThrowHresult(char const * func, char const * file, int line, HRESULT h)
{
  static char buf[4096];
  sprintf(buf, "Error saving .X file: 0x%08x\n%s:%d: %s\n", h, file, line, func);
  buf[4095] = 0;
  throw buf;
}




static TCallback gCallback;

extern "C" {

PCHAR DLL_EXPORT __stdcall PluginName()
{
  return (PCHAR)"NuXporter .X Output";
}

PCHAR DLL_EXPORT __stdcall PluginDescription()
{
  return (PCHAR)"Export .X files with proper normals and hierarchy.";
}

PCHAR DLL_EXPORT __stdcall PluginDeleDVersion()
{
  return (PCHAR)"1.1";
}

PCHAR DLL_EXPORT __stdcall PluginVersion()
{
  return (PCHAR)"1.1";
}

PCHAR DLL_EXPORT __stdcall PluginAuthor()
{
  return (PCHAR)"Jon Watte";
}

PCHAR DLL_EXPORT __stdcall PluginEmail()
{
  return (PCHAR)"deled-nux@mindcontrol.org";
}

static LRESULT CALLBACK MessageProc(int, WPARAM, LPARAM);
static MODULEINFO gModuleInformation;

void DLL_EXPORT __stdcall PluginSetCallback(void * ptr)
{
  if (gCallback == NULL) {
    ::InitCommonControls();
  }
  //  When starting up, capture state of host application
  gCallback = TCallback(ptr);

  if (hModule == NULL) {
    hModule = ::GetModuleHandle("NuXporter.dll"); //  hope nobody renamed me!
  }
  if (hModule == NULL) {
    ::MessageBox(0, 
        "NuXporter can't find the dialog resources!\nNuXporter won't work correctly.", 
        "NuXporter", MB_OK);
  }
}

static void SaveScene(IScene *, char const *);

static void DoExport()
{
  try {
	  AutoDispose<IScene> scene(NewScene(gCallback));
    SaveScene(scene.get(), settings.path.c_str());
  }
  catch(...) {
    ::MessageBox(0, 
        "Unknown exception while exporting .X file.\nSorry this message can't be more helpful.", 
        "NuXporter Error", MB_OK);
  }
}

static bool DoSaveFileDialog()
{
  OPENFILENAME saveDialog;
  char szFile[512];

  ZeroMemory(&saveDialog, sizeof(OPENFILENAME));
  ZeroMemory(szFile, sizeof(szFile));

  saveDialog.lStructSize = sizeof(OPENFILENAME);
  saveDialog.hwndOwner = hDialog;
  saveDialog.lpstrFile = szFile;
  saveDialog.nMaxFile = sizeof(szFile);
  saveDialog.lpstrFilter = "X Files(*.x)\0*.x\0All files(*.*)\0*.*\0";
  saveDialog.nFilterIndex = 0;
  saveDialog.lpstrFileTitle = NULL;
  saveDialog.lpstrTitle = "Export .X from DeleD";
  saveDialog.lpstrInitialDir = NULL;
  saveDialog.Flags = OFN_OVERWRITEPROMPT;

	if(!::GetSaveFileName(&saveDialog)) {
	  return false;
  }

  settings.path = szFile;
  ::SetDlgItemText(hDialog, IDC_FILEPATH, szFile);
  return true;
}

static bool SettingsOK(bool kvetch = true)
{
  if (settings.path.length() < 6) {
    if (kvetch) {
      ::MessageBox(0, (std::string("The path chosen is too short:\n") + settings.path).c_str(),
          "NuXporter", MB_OK);
    }
    return false;
  }
  if (settings.scaleDenom < 1 || settings.scaleNumer < 0.001f || settings.scaleNumer > 1001) {
    if (kvetch) {
      ::MessageBox(0, "The scale chosen is not legal.", "NuXporter", MB_OK);
    }
    return false;
  }
  std::string s = settings.path.substr(0, settings.path.find_last_of('\\'));
  DWORD fa = ::GetFileAttributes(s.c_str());
  if ((fa & FILE_ATTRIBUTE_DIRECTORY) == 0) {
    if (kvetch) {
      ::MessageBox(0, (std::string("The directory does not exist:\n")+s).c_str(), 
          "NuXporter", MB_OK);
    }
    return false;
  }
  return true;
}

static void UpdateDerivedControls()
{
  if (!::IsDlgButtonChecked(hDialog, IDC_MATERIALS) &&
      (::IsDlgButtonChecked(hDialog, IDC_COPYTEXTURES) || ::IsDlgButtonChecked(hDialog, IDC_TEXTUREPATHS))) {
    ::CheckDlgButton(hDialog, IDC_COPYTEXTURES, false);
    ::CheckDlgButton(hDialog, IDC_TEXTUREPATHS, false);
    settingsDirty = true;
  }
  bool b = ::IsDlgButtonChecked(hDialog, IDC_MATERIALS) ? true : false;
  ::EnableWindow(::GetDlgItem(hDialog, IDC_COPYTEXTURES), b);
  ::EnableWindow(::GetDlgItem(hDialog, IDOK), SettingsOK(false));
}

static void ReadSettings()
{
  std::string fn;
  GetSettingsFilename(fn);
  FILE * f = fopen(fn.c_str(), "rb");
  if (f != NULL) {
    fseek(f, 0, 2);
    long l = ftell(f);
    fseek(f, 0, 0);
    char * buf = new char[l+1];
    fread(buf, 1, l, f);
    buf[l] = 0;
    fclose(f);

    class SS {
      public:
        char const * str_;
        SS(char const * str) : str_(str) {}
        void doBool(int id, char const * name, bool & b) {
          b = getarg(name) == "true";
        }
        void doString(int id, char const * name, std::string & str) {
          str = getarg(name);
        }
        void doRatio(int id, char const * name, float & numer, float & denom) {
          numer = 1;
          denom = 1;
          sscanf(getarg(name).c_str(), " %f / %f", &numer, &denom);
        }
        void doRadio(int const * ids, char const * name, int & value) {
          value = atoi(getarg(name).c_str());
        }
        std::string getarg(char const * name) {
          std::string fnd = std::string("\n") + name + "=";
          char const * pos = strstr(str_, fnd.c_str());
          if (pos) {
            pos += fnd.length();
            char const * end = strchr(pos, '\n');
            return std::string(pos, end ? end-pos : strlen(pos));
          }
          return std::string("");
        }
    } ss(buf);
    VisitSettings(settings, ss);
    for (int i = 0; i < sizeof(oldEffects)/sizeof(oldEffects[0]); ++i) {
      char buf[100];
      sprintf(buf, "oldEffects[%d]", i);
      oldEffects[i] = ss.getarg(buf);
    }
    for (int i = 0; i < sizeof(oldFiles)/sizeof(oldFiles[0]); ++i) {
      char buf[100];
      sprintf(buf, "oldFiles[%d]", i);
      oldFiles[i] = ss.getarg(buf);
    }
    for (int i = 0; i < sizeof(oldScales)/sizeof(oldScales[0]); ++i) {
      char buf[100];
      sprintf(buf, "oldScales[%d]", i);
      oldScales[i] = ss.getarg(buf);
    }
    
    delete[] buf;
    settingsDirty = false;
    //  substitute this scene name for the last item in the path
    std::string s = settings.path.substr(0, settings.path.find_last_of('\\'));
    s += "\\";
    s += convert_name(GetSceneName());
    s += ".x";
    settings.path = s;
  }
}

static void ApplySettings()
{
  UpdateCombo(oldEffects, sizeof(oldEffects)/sizeof(oldEffects[0]), IDC_EFFECT);
  UpdateCombo(oldFiles, sizeof(oldFiles)/sizeof(oldFiles[0]), IDC_FILEPATH);
  UpdateCombo(oldScales, sizeof(oldScales)/sizeof(oldScales[0]), IDC_SCALE);

  class {
    public:
      void doBool(int id, char const * name, bool & b) {
        ::CheckDlgButton(hDialog, id, b ? BST_CHECKED : BST_UNCHECKED);
      }
      void doString(int id, char const * name, std::string & str) {
        ::SetDlgItemText(hDialog, id, str.c_str());
      }
      void doRatio(int id, char const * name, float & numer, float & denom) {
        char buf[100];
        if (denom != 1) {
          sprintf(buf, "%g/%g", numer, denom);
        }
        else {
          sprintf(buf, "%.3f", numer);
        }
        ::SetDlgItemText(hDialog, id, buf);
      }
      void doRadio(int const * ids, char const * name, int & value) {
        for (int i = 0; ids[i]; ++i) {
          ::CheckDlgButton(hDialog, ids[i], value == i ? BST_CHECKED : BST_UNCHECKED);
        }
      }
  } ss;
  VisitSettings(settings, ss);
  settingsDirty = false;  //  I just set them!
}

static void ExtractSettings()
{
  class {
    public:
      void doBool(int id, char const * name, bool & b) {
        b = ::IsDlgButtonChecked(hDialog, id) ? true : false;
      }
      void doString(int id, char const * name, std::string & str) {
        size_t s = 512;
        str.resize(s);
        s = ::GetDlgItemText(hDialog, id, &str[0], (int)s);
        str.resize(s);
      }
      void doRatio(int id, char const * name, float & numer, float & denom) {
        char buf[100];
        size_t s = ::GetDlgItemText(hDialog, id, buf, sizeof(buf));
        if (s == 100) s = 99;
        buf[s] = 0;
        int i = sscanf(buf, " %f / %f", &numer, &denom);
        if (i == 0) {
          numer = denom = 1;
        }
        else if (i == 1) {
          denom = 1;
        }
      }
      void doRadio(int const * ids, char const * name, int & value) {
        value = 0;
        for (int i = 0; ids[i]; ++i) {
          if (::IsDlgButtonChecked(hDialog, ids[i])) {
            value = i;
            break;
          }
        }
      }
  } ss;
  VisitSettings(settings, ss);
  UpdateArray(oldFiles, sizeof(oldFiles)/sizeof(oldFiles[0]), settings.path);
  UpdateArray(oldEffects, sizeof(oldEffects)/sizeof(oldEffects[0]), settings.effect);
  char buf[100];
  sprintf(buf, "%g/%g", settings.scaleNumer, settings.scaleDenom);
  std::string ratio(buf);
  UpdateArray(oldScales, sizeof(oldScales)/sizeof(oldScales[0]), ratio);
}

static void SaveSettings()
{
  std::string fn;
  GetSettingsFilename(fn);
  FILE * f = fopen(fn.c_str(), "wb");
  if (f) {
    fprintf(f, "version=1\n" );
    class SS {
      public:
        FILE * f_;
        SS(FILE * f) : f_(f) {}
        void doBool(int id, char const * name, bool & b) {
          fprintf(f_, "%s=%s\n", name, b ? "true" : "false");
        }
        void doString(int id, char const * name, std::string & str) {
          fprintf(f_, "%s=%s\n", name, str.c_str());
        }
        void doRatio(int id, char const * name, float & numer, float & denom) {
          if (denom == 1) {
            fprintf(f_, "%s=%.3f\n", name, numer);
          }
          else {
            fprintf(f_, "%s=%g/%g\n", name, numer, denom);
          }
        }
        void doRadio(int const * ids, char const * name, int & value) {
          fprintf(f_, "%s=%d\n", name, value);
        }
    } ss(f);
    VisitSettings(settings, ss);
    WriteArray(oldFiles, 5, "oldFiles", f);
    WriteArray(oldEffects, 5, "oldEffects", f);
    WriteArray(oldScales, 5, "oldScales", f);
    fclose(f);
    settingsDirty = false;
  }
  else {
    ::OutputDebugString("Could not save settings file for NuXporter\n");
  }
}

static void DlgCommand(int cmd, HWND dlg, HWND ctl)
{
#if !defined(NDEBUG)
  char buf[256];
  sprintf(buf, "NuXporter: DlgCommand %d 0x%lx 0x%lx\n", cmd, (size_t)dlg, (size_t)ctl);
  ::OutputDebugString(buf);
#endif

  bool dirtySettings = false;

  switch (cmd) {
    case IDCANCEL:
      ::DestroyWindow(hDialog);
      break;
    case IDOK:
      if (settingsDirty) {
        ExtractSettings();
      }
      if (SettingsOK()) {
        if (settingsDirty) {
          SaveSettings();
        }
        ::DoExport();
        ::DestroyWindow(hDialog);
      }
      break;
    case IDC_SCALE:
      dirtySettings = true;
      break;
    case IDC_BROWSE:
      dirtySettings = DoSaveFileDialog();
      break;
    case IDC_FILEPATH:
    case IDC_EFFECT:
    case IDC_TEXTFORMAT:
    case IDC_BINARYFORMAT:
    case IDC_NORMALS:
    case IDC_TEXCOORDS:
    case IDC_MAPCOORDS:
    case IDC_MATERIALS:
    case IDC_COPYTEXTURES:
    case IDC_TEXTUREPATHS:
    case IDC_IDENTITYROOT:
    case IDC_FLOORZERO:
      dirtySettings = true;
      break;
    case IDC_MIRRORZ:
    default:
      settingsDirty = true; //  doesn't call updatederivedcontrols
      break;
  }
  if (dirtySettings) {
    UpdateDerivedControls();
    settingsDirty = true;
  }
}

static INT_PTR CALLBACK DlgProc(HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam)
{
  INT_PTR ret = true;

  switch (msg) {
    case WM_CLOSE:
      DlgCommand(IDCANCEL, dlg, ::GetDlgItem(dlg, IDCANCEL));
      break;
    case WM_COMMAND:
      DlgCommand((int)(wparam & 0xffff), dlg, (HWND)lparam);
      break;
    case WM_INITDIALOG:
      ::OutputDebugString("NuXporter: WM_INITDIALOG()\n");
      hDialog = dlg;
      ReadSettings();
      ApplySettings();
      UpdateDerivedControls();
      break;
    case WM_DESTROY:
      hDialog = NULL;
      ::OutputDebugString("NuXporter: unhooking GetMessage()\n");
      ::UnhookWindowsHookEx(hHook);
      hHook = NULL;
      break;
    default:
      ret = 0;
      break;
    case WM_LBUTTONDBLCLK:
    case WM_MBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_SIZE:
    case WM_NCDESTROY:
    case WM_SETFONT:
    case WM_NOTIFYFORMAT:
    case WM_QUERYUISTATE:
    case WM_CHANGEUISTATE:
    case WM_SHOWWINDOW:
    case WM_WINDOWPOSCHANGING:
    case WM_WINDOWPOSCHANGED:
    case WM_GETICON:
    case WM_NCCALCSIZE:
    case WM_NCACTIVATE:
    case WM_ACTIVATE:
    case WM_SETFOCUS:
    case WM_KILLFOCUS:
    case WM_NCPAINT:
    case WM_ERASEBKGND:
    case WM_POWERBROADCAST:
    case WM_DEVICECHANGE:
    case WM_ACTIVATEAPP:
    case WM_CTLCOLORBTN:
    case WM_CTLCOLORDLG:
    case WM_PAINT:
    case WM_CTLCOLORSTATIC:
    case WM_CTLCOLOREDIT:
    case WM_NCHITTEST:
    case WM_SETCURSOR:
    case WM_MOUSEMOVE:
    case WM_MOUSEACTIVATE:
    case WM_USER:
    case WM_NCMOUSEMOVE:
    case WM_NCMOUSELEAVE:
    case WM_NOTIFY:
    case WM_PARENTNOTIFY:
    case WM_NCLBUTTONDOWN:
    case WM_SYSCOMMAND:
    case WM_CAPTURECHANGED:
    case WM_GETMINMAXINFO:
    case WM_ENTERSIZEMOVE:
    case WM_MOVING:
    case WM_MOVE:
    case WM_EXITSIZEMOVE:
    case WM_UPDATEUISTATE:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_CANCELMODE:
      ret = 0;
      break;
  }
  
  return ret;
}

static BOOL CALLBACK ThreadWindowsProc(HWND hwnd, LPARAM)
{
  char buf[512];
  ::GetWindowText(hwnd, buf, 512);
  buf[511] = 0;
  if (hMainWindow == NULL || strstr(buf, ".dmf") != NULL) {
    hMainWindow = hwnd;
  }
  ::OutputDebugString((std::string("Top level window: ") + buf + "\n").c_str());
  return true;
}

/**This executes plugin code*/
void DLL_EXPORT __stdcall PluginExecute()
{
  if (hDialog == NULL) {
    //  let's assume the plug-in is called in the context of the main window
    ::OutputDebugString("NuXporter: hooking GetMessage()\n");
    ::EnumThreadWindows(::GetCurrentThreadId(), ThreadWindowsProc, 0);
    hHook = ::SetWindowsHookEx(WH_GETMESSAGE, MessageProc, hModule, ::GetCurrentThreadId());
    hDialog = ::CreateDialog(hModule, MAKEINTRESOURCE(IDD_DIALOG1), 0, DlgProc);
    ::ShowWindow(hDialog, SW_SHOWNORMAL);
  }
  else {
    ::BringWindowToTop(hDialog);
  }
}

LRESULT DLL_EXPORT __stdcall PluginCommit()
{
  //  called when installer is done committing installation
  ::ShellExecute(0, "open", "http://www.mindcontrol.org/~hplus/graphics/nuxporter.html", 0, 0, SW_SHOW);
  return 0;
}

static HRESULT hr;

#define CHECK(x) \
  hr = x; if( FAILED(hr) ) { ThrowHresult(#x,__FILE__,__LINE__,hr); }

#pragma pack(push)
#pragma pack(4)
struct DX_ColorRGB {
  float r;
  float g;
  float b;
};
struct DX_ColorRGBA : public DX_ColorRGB {
  float a;
};
struct DX_Material {
  DX_ColorRGBA diffuse;
  float power;
  DX_ColorRGB specular;
  DX_ColorRGB emissive;
};
struct DX_Vector : public D3DXVECTOR3 {
  DX_Vector() { x = 0; y = 0; z = 0; }
  DX_Vector(Float3 const & v) { x = float(v.x_); y = float(v.y_); z = float(v.z_); }
  DX_Vector& operator=(D3DXVECTOR3 const & v) { *(D3DXVECTOR3*)this = v; return *this; }
};
struct DX_Coord2 {
  DX_Coord2() { u = 0; v = 0; }
  DX_Coord2(Float2 const & uv) { u = float(uv.u_); v = float(uv.v_); }
  float u;
  float v;
};
struct DX_Float3Decl {
  DWORD type;
  DWORD method;
  DWORD usage;
  DWORD index;
};
#pragma pack(pop)
}
//  ExtractMeshData will not weld the mesh between 
//  polys; this could be done as a separate pass.
static void ExtractMeshData(
  IScene * scene,
  IObject * object,
  vector2<char> & mesh,
  vector2<char> & normals,
  vector2<char> & texcoords,
  vector2<char> & colors,
  vector2<char> & duplication,
  vector2<char> & materialData,
  vector2<DX_Material> & materials,
  vector2<std::string> & materialTextures,
  vector2<char> & smapcoords,
  vector2<std::string> & smapTextures,
  float * matrix)
{
  _CrtCheckMemory();

  DWORD ixBase = 0;
  vector2<DWORD> iIndices;
  vector2<DX_Vector> iVerts;
  vector2<DX_Vector> oVerts;
  vector2<DWORD> normalIndices;
  vector2<DX_Vector> iNormals;
  vector2<DX_Coord2> iCoords;
  vector2<DX_ColorRGBA> iColors;
  vector2<DWORD> iDuplication;
  vector2<DWORD> iMaterials;
  vector2<DX_Coord2> sCoords;
  bool haveSmaps = false;

  vector2<DWORD> remapVerts;
  vector2<DWORD> remapMaterials;
  vector2<DWORD> materialLayers;

  IInterface<Float3> * verticesItf = object->vertices();
  Float3 const * vertices = verticesItf->begin();
  iVerts.resize(verticesItf->count());
  size_t nVerts = iVerts.size();
  size_t maxNVerts = iVerts.size();
  remapVerts.resize(nVerts);
  std::fill(remapVerts.begin(), remapVerts.end(), 0xffffffff);
  for (size_t v = 0; v < nVerts; ++v) {
    iVerts[v] = DX_Vector(vertices[v]);
  }

  IInterface<IMaterial *> * materialsItf = scene->materials();
  IMaterial * const * materialsPtr = materialsItf->begin();
  remapMaterials.resize(materialsItf->count());
  materialLayers.resize(remapMaterials.size());
  std::fill(remapMaterials.begin(), remapMaterials.end(), 0xffffffff);
  for (size_t l = 0; l < materialLayers.size(); ++l) {
    materialLayers[l] = (DWORD)materialsPtr[l]->layers()->count();
  }

  IInterface<Poly> * polygonsItf = object->polygons();
  Poly const * polygons = polygonsItf->begin();
  size_t nPolys = polygonsItf->count();
  for (size_t p = 0; p < nPolys; ++p) {
    Poly const & poly = polygons[p];
    //  calculate polygon normal
    size_t nVerts = poly.faceSize();
    DX_Vector normal;
    //  I'm assuming convex polygons here
    DX_Vector a = iVerts[poly.indices()[0]], 
        b = iVerts[poly.indices()[1]], 
        c = iVerts[poly.indices()[2]];
    b -= a;
    c -= a;
    
    DWORD i = 3;
    D3DXVec3Cross(&normal, &c, &b);
    while (D3DXVec3LengthSq(&normal) < 1e-6 && i < nVerts) {
      c = iVerts[poly.indices()[i]] - a;
      ++i;
      D3DXVec3Cross(&normal, &c, &b);
    }
    D3DXVec3Normalize(&normal, &normal);

    iNormals.push_back(normal);
    if (remapMaterials[poly.material_] == 0xffffffff) {
      remapMaterials[poly.material_] = (DWORD)materials.size();
      DX_Material m;
      m.diffuse.r = m.diffuse.g = m.diffuse.b = m.diffuse.a = 1;
      m.power = 40;
      m.specular = m.diffuse;
      m.emissive.r = m.emissive.g = m.emissive.b = 0;
      materials.push_back(m);
      DWORD ml = materialLayers[poly.material_];
      if (ml > 0) {
        std::string const & file = materialsPtr[poly.material_]->layers()->item(0)->texture();
        if (file.find_last_of('\\') == std::string::npos) {
          ::OutputDebugString((std::string("Found badly-named texture: ") + file + "\n").c_str());
          materialTextures.push_back("");
        }
        else {
          materialTextures.push_back(file);
        }
        if (ml > 1) {
          std::string const & smap = materialsPtr[poly.material_]->layers()->item(ml-1)->texture();
          if (smap.find_last_of('\\') == std::string::npos) {
            ::OutputDebugString((std::string("Found badly-named texture: ") + smap + "\n").c_str());
            smapTextures.push_back("");
          }
          else {
            smapTextures.push_back(smap);
          }
        }
        else {
          smapTextures.push_back(std::string(""));
        }
      }
      else {
        materialTextures.push_back(std::string(""));
        smapTextures.push_back(std::string(""));
      }
    }

    for (size_t i = 0; i < nVerts; ++i) {
      DWORD ix = poly.indices()[i];
      if (ix >= maxNVerts) {
        static char buf[512];
        sprintf( buf, "Input data is mal-formed (vertex index %d out of range %d)", ix, nVerts );
        throw buf;
      }
      if (remapVerts[ix] == 0xffffffff) {
        remapVerts[ix] = (DWORD)oVerts.size();
        oVerts.push_back(iVerts[ix]);
        DWORD lc = materialLayers[poly.material_];
        if (lc > 0) {
          iCoords.push_back(poly.coords(0)[i]);
          if (lc > 1) {
            sCoords.push_back(poly.coords(lc-1)[i]);
            haveSmaps = true;
          }
          else {
            sCoords.push_back(DX_Coord2(Float2(0, 0)));
          }
        }
        else {
          iCoords.push_back(DX_Coord2(Float2(0, 0)));
          sCoords.push_back(DX_Coord2(Float2(0, 0)));
        }
        iDuplication.push_back(ix);
      }
      if (i > 1) {
        //  emit triangle list of polygon (as fan)
        iIndices.push_back(3);
        iIndices.push_back(remapVerts[poly.indices()[0]]);
        if (settings.mirrorZ) {
          iIndices.push_back(remapVerts[poly.indices()[i-1]]);
          iIndices.push_back(remapVerts[poly.indices()[i]]);
        }
        else {
          iIndices.push_back(remapVerts[poly.indices()[i]]);
          iIndices.push_back(remapVerts[poly.indices()[i-1]]);
        }
        iMaterials.push_back(remapMaterials[poly.material_]);
        normalIndices.push_back(3);
        normalIndices.push_back((DWORD)iNormals.size()-1);
        normalIndices.push_back((DWORD)iNormals.size()-1);
        normalIndices.push_back((DWORD)iNormals.size()-1);
      }
    }
    //  put remapVerts back the way it was
    for (size_t i = 0; i < nVerts; ++i) {
      DWORD ix = poly.indices()[i];
      remapVerts[ix] = 0xffffffff;
    }
    if ((p & 63) == 63) {
      LocalProgress(p, nPolys);
    }
  }

  if (!haveSmaps) {
    sCoords.clear();
  }

  //  scale vertices.
  //  calculate center of mesh, and re-scale vertices; then 
  //  emit an appropriate matrix.
  if (oVerts.size() != 0) {
    D3DXVECTOR3 lower = oVerts[0], upper = oVerts[0];
    size_t ovCnt = oVerts.size();
    assert(settings.scaleDenom > 0);
    assert(settings.scaleNumer > 0);
    float scale = settings.scaleNumer / settings.scaleDenom;
    for (size_t v = 0; v < ovCnt; ++v) {
      oVerts[v] *= scale;
      D3DXVec3Minimize(&lower, &lower, &oVerts[v]);
      D3DXVec3Maximize(&upper, &upper, &oVerts[v]);
    }
    if (settings.center || settings.floorZero) {
      D3DXVECTOR3 offset(0, 0, 0);
      if (settings.center) {
        offset = (lower+upper) * -0.5f;
      }
      if (settings.floorZero) {
        offset.y = -lower.y;
      }
      for (size_t v = 0; v < ovCnt; ++v) {
        oVerts[v] += offset;
      }
      if (!settings.identityRoot) {
        matrix[12] = -offset.x;
        matrix[13] = -offset.y;
        matrix[14] = -offset.z;
      }
    }
    if (settings.mirrorZ) {
      for (size_t v = 0; v < ovCnt; ++v) {
        oVerts[v].z = -oVerts[v].z;
      }
      matrix[14] = -matrix[14];
    }
  }

  //  generate mesh
  DWORD dw = (DWORD)oVerts.size();
  if (dw != 0) {
    mesh.append((char const *)&dw, sizeof(dw));
    mesh.append((char const *)&oVerts[0], dw*sizeof(DX_Vector));
    dw = (DWORD)iIndices.size()/4;   //  each triangle is prefixed by '3'
    mesh.append((char const *)&dw, sizeof(dw));
    mesh.append((char const *)&iIndices[0], iIndices.size()*sizeof(DWORD));
  }

  //  generate normals
  dw = (DWORD)iNormals.size();
  if (dw != 0) {
    normals.append((char const *)&dw, sizeof(dw));
    normals.append((char const *)&iNormals[0], dw*sizeof(DX_Vector));
    dw = (DWORD)normalIndices.size()/4;   //  each triangle is prefixed by '3'
    normals.append((char const *)&dw, sizeof(dw));
    normals.append((char const *)&normalIndices[0], normalIndices.size()*sizeof(DWORD));
  }

  //  generate texcoords
  dw = (DWORD)iCoords.size();
  if (dw != 0) {
    texcoords.append((char const *)&dw, sizeof(dw));
    texcoords.append((char const *)&iCoords[0], dw*sizeof(DX_Coord2));
  }

  //  shadow map coords
  dw = (DWORD)sCoords.size();
  if (dw != 0) {
    smapcoords.append((char const *)&dw, sizeof(dw));
    smapcoords.append((char const *)&sCoords[0], dw*sizeof(DX_Coord2));
  }

  //  generate colors
  dw = (DWORD)iColors.size();
  if (dw != 0) {
    colors.append((char const *)&dw, sizeof(dw));
    colors.append((char const *)&iColors[0], dw*sizeof(DX_ColorRGBA));
  }

  //  generate duplication data
  dw = (DWORD)iDuplication.size();
  if (dw != 0) {
    assert(iDuplication.back() < maxNVerts);
    assert(iDuplication.size() == oVerts.size());
    duplication.append((char const *)&dw, sizeof(dw));
    dw = (DWORD)maxNVerts;
    duplication.append((char const *)&dw, sizeof(dw));
    duplication.append((char const *)&iDuplication[0], iDuplication.size()*sizeof(DWORD));
  }

  //  generate material data
  dw = (DWORD)materials.size();
  if (dw != 0) {
    materialData.append((char const *)&dw, sizeof(dw));
    dw = (DWORD)iMaterials.size();
    materialData.append((char const *)&dw, sizeof(dw));
    materialData.append((char const *)&iMaterials[0], dw*sizeof(DWORD));
  }
}

std::set<std::string> texturesToCopy;

extern "C" {

static void SaveObject(IScene * scene, IObject * object, ID3DXFileSaveData * parent)
{
  CComPtr<ID3DXFileSaveData> frame;
  std::string n = convert_name(object->name());
  ::OutputDebugString((std::string("Saving object: ") + object->name() + "\n").c_str());
  CHECK( parent->AddDataObject(TID_D3DRMFrame, n.c_str(), 0, 0, 0, &frame) );

  //  extract the mesh
  float mat[4][4] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
  vector2<char> mesh;
  vector2<char> normals;
  vector2<char> texcoords;
  vector2<char> colors;
  vector2<char> duplication;
  vector2<char> materialData;
  vector2<DX_Material> materials;
  vector2<std::string> materialTextures;
  vector2<char> smapcoords;
  vector2<std::string> smapTextures;

  ExtractMeshData(scene, object, mesh, normals, texcoords, colors, duplication, 
      materialData, materials, materialTextures, smapcoords, smapTextures, &mat[0][0]);

  CComPtr<ID3DXFileSaveData> temp;
  CHECK( frame->AddDataObject(TID_D3DRMFrameTransformMatrix, "xform", 0, sizeof(mat), mat, &temp) );
  temp = 0;

  CComPtr<ID3DXFileSaveData> stored;
  std::string t = convert_name(object->tag());
  CHECK( frame->AddDataObject(TID_D3DRMMesh, t.c_str(), 0, 
      mesh.size(), &mesh[0], &stored) );

  if (normals.size() && settings.normals) {
    CHECK( stored->AddDataObject(TID_D3DRMMeshNormals, 0, 0, 
        normals.size(), &normals[0], &temp) );
    temp = 0;
  }

  if (texcoords.size() && settings.texcoords) {
    CHECK( stored->AddDataObject(TID_D3DRMMeshTextureCoords, 0, 0, 
        texcoords.size(), &texcoords[0], &temp) );
    temp = 0;
  }

  if (colors.size()) {
    CHECK( stored->AddDataObject(TID_D3DRMMeshVertexColors, 0, 0, 
        colors.size(), &colors[0], &temp) );
    temp = 0;
  }

  if (duplication.size() && 0) {  //  clearly, I'm not generating these right
    CHECK( stored->AddDataObject(DXFILEOBJ_VertexDuplicationIndices, 0, 0, 
        duplication.size(), &duplication[0], &temp) );
    temp = 0;
  }
  
  if (smapcoords.size() && settings.mapcoords) {
    vector2<char> mapcoords;
    DWORD d = 1;
    mapcoords.append((char const *)&d, sizeof(d));
    DX_Float3Decl vd;
    vd.type = D3DDECLTYPE_FLOAT2;
    vd.method = D3DDECLMETHOD_DEFAULT;
    vd.usage = D3DDECLUSAGE_TEXCOORD;
    vd.index = 1;
    mapcoords.append((char const *)&vd, sizeof(vd));
    d = DWORD(smapcoords.size()/sizeof(DWORD))-1;
    mapcoords.append((char const *)&d, sizeof(d));
    mapcoords.append(&smapcoords[sizeof(DWORD)], 
        smapcoords.size()-sizeof(DWORD));
    CHECK( stored->AddDataObject(DXFILEOBJ_DeclData, 0, 0, 
        mapcoords.size(), &mapcoords[0], &temp) );
    temp = 0;
  }

  if (materials.size() != 0 && settings.materials) {
    std::string effect = settings.effect;
    if (settings.mapcoords && smapcoords.size() && effect.length() == 0) {
      effect = "shadowmap.fx";
    }
    CComPtr<ID3DXFileSaveData> mtl;
    CComPtr<ID3DXFileSaveData> mtllist;
    CHECK( stored->AddDataObject(TID_D3DRMMeshMaterialList, 0, 0,
        materialData.size(), &materialData[0], &mtllist) );
    for (size_t i = 0; i < materials.size(); ++i) {
      CHECK( mtllist->AddDataObject(TID_D3DRMMaterial, 0, 0,
          sizeof(DX_Material), &materials[0], &mtl) );
      if (materialTextures[i].length()) {
        std::string texName = materialTextures[i];
        //  perhaps reference a shorter path
        if (!settings.texturePaths) {
          texName = texName.substr(texName.find_last_of('\\')+1);
        }
        CHECK( mtl->AddDataObject(TID_D3DRMTextureFilename, 0, 0,
            texName.length()+1, texName.c_str(), &temp) );
        temp = 0;
        if (settings.copyTextures) {
          //  always copy the full path
          ::OutputDebugString((std::string("Will copy texture: ") + materialTextures[i] + "\n").c_str());
          texturesToCopy.insert(materialTextures[i]);
          if (settings.mapcoords && smapTextures[i].length()) {
            ::OutputDebugString((std::string("Will copy shadow map: ") + smapTextures[i] + "\n").c_str());
            texturesToCopy.insert(smapTextures[i]);
          }
        }
      }
      if (effect.length()) {
        CComPtr<ID3DXFileSaveData> fx;
        CHECK( mtl->AddDataObject(DXFILEOBJ_EffectInstance, 0, 0, 
            effect.length()+1, effect.c_str(), &fx) );
        if (settings.mapcoords && smapTextures[i].length()) {
          vector2<char> data;
          data.append("ShadowMap", 10);
          std::string texName = smapTextures[i];
          //  perhaps reference a shorter path
          if (!settings.texturePaths) {
            texName = texName.substr(texName.find_last_of('\\')+1);
          }
          data.append(texName.c_str(), texName.length()+1);
          CHECK( fx->AddDataObject(DXFILEOBJ_EffectParamString, 0, 0,
              data.size(), &data[0], &temp) );
          temp = 0;
        }
      }
      mtl = 0;
    }
  }
}


static void CopyTexture(std::string input, std::string output)
{
  output = output.substr(0, output.find_last_of('\\'));
  if (!settings.texturePaths) {
    output += input.substr(input.find_last_of('\\'));
  }
  else {
    output += std::string("\\") + input;
    std::string dir = output.substr(0, output.find_last_of('\\'));
    ::CreateDirectory(dir.c_str(), 0);
  }
  std::string textures;
  GetTexturesFilename(textures);
  if (FALSE == ::CopyFile((textures + "\\" + input).c_str(), output.c_str(), FALSE)) {
    ::MessageBox(0, (std::string("Could not copy texture:\n") + input + "\n" + output).c_str(),
        "NuXporter", MB_OK);
  }
}

static void SaveScene(IScene * scene, char const * name)
{
  texturesToCopy.clear();
  UpdateProgress(1, 100);
  UpdateTask(std::string("Reading ") + name, 1);

  size_t progressVertices = 300;
  size_t progressTextures = 0;
  size_t n = scene->objects()->count();
  for (size_t i = 0; i < n; ++i) {
    progressVertices += scene->objects()->item(i)->vertices()->count();
  }
  progressTextures = scene->materials()->count();
  if (settings.copyTextures) {
    progressTextures *= 300;
  }

  size_t totalProgress = 300;
  UpdateProgress(totalProgress, progressVertices+progressTextures);

  try {
    CComPtr<ID3DXFile> file;
    CHECK( D3DXFileCreate(&file) );
    static char const * xExtensions = XEXTENSIONS_TEMPLATES;
    static char const * xSkinExp = XSKINEXP_TEMPLATES;
    CHECK( file->RegisterTemplates((void*)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES) );
    CHECK( file->RegisterTemplates((void*)xExtensions, strlen(xExtensions)) );
    CHECK( file->RegisterTemplates((void*)xSkinExp, strlen(xSkinExp)) );
    CComPtr<ID3DXFileSaveObject> save;
    CHECK( file->CreateSaveObject(name, D3DXF_FILESAVE_TOFILE, 
        settings.format == 0 ? D3DXF_FILEFORMAT_TEXT : (D3DXF_FILEFORMAT_BINARY | D3DXF_FILEFORMAT_COMPRESSED), 
        &save) );
    CComPtr<ID3DXFileSaveData> root;
    std::string n = convert_name(scene->name());
    CHECK( save->AddDataObject(TID_D3DRMFrame, n.c_str(), 0, 0, 0, &root) );

    size_t nObjects = scene->objects()->count();
    for (size_t i = 0; i < nObjects; ++i) {
      IObject * o = scene->objects()->item(i);
      UpdateTask(std::string("Mesh ") + o->tag(), o->vertices()->count());
      SaveObject(scene, o, root);
      totalProgress += o->vertices()->count();
      UpdateProgress(totalProgress, progressVertices+progressTextures);
    }

    CHECK( save->Save() );

    if (settings.copyTextures) {
      for (std::set<std::string>::iterator ptr = texturesToCopy.begin(), end = texturesToCopy.end();
          ptr != end; ++ptr) {
        UpdateTask(*ptr, 300);
        CopyTexture(*ptr, settings.path);
        totalProgress += 300;
        UpdateProgress(totalProgress, progressVertices+progressTextures);
      }
    }
    totalProgress = progressVertices+progressTextures;
    UpdateTask("Complete.", 1);
    UpdateProgress(totalProgress, progressVertices+progressTextures);
  }
  catch(char const * str) {
    std::string s = "Error while saving file:\n";
    char buf[128];
    sprintf(buf, "Error code: 0x%08x\n", hr);
    s += buf;
    s += str;
    MessageBox(NULL, s.c_str(), "NuXporter Error", MB_OK);
  }
  catch(...) {
    MessageBox(NULL, "An unknown error occurred while exporting.", "NuXporter Error", MB_OK);
  }
}

//  The hooking of the host application is somewhat hacky, 
//  but it seems to work OK.

static LRESULT CALLBACK MessageProc(int code, WPARAM wparam, LPARAM lparam)
{
  LRESULT ret = ::CallNextHookEx(hHook, code, wparam, lparam);
  switch (code) {
    case HC_ACTION:
      if (wparam == PM_REMOVE) {
        MSG * msg = (MSG *)lparam;
        __try {
          if (msg->hwnd == hDialog && ::IsDialogMessage(hDialog, msg)) {
  #if !defined(NDEBUG)
            if (msg->message != WM_MOUSEMOVE && msg->message != WM_NCMOUSEMOVE) {
              char buf[256];
              sprintf(buf, "NuXporter: eating message 0x%04x for 0x%lx\n", msg->message, (size_t)msg->hwnd);
              ::OutputDebugString(buf);
            }
  #endif
            switch (msg->message) {
              case WM_NCLBUTTONDOWN:
              case WM_NCMOUSEMOVE:
              case WM_NCLBUTTONUP:
              case WM_LBUTTONUP:
              case WM_MOUSEMOVE:
                //  Don't nuke these messages; they are needed 
                //  for drag-movement of dialogs. Bah!
                break;
              default:
                msg->message = WM_NULL;
                msg->lParam = 0;
                msg->wParam = 0;
                break;
            }
          }
          else if (msg->hwnd == hMainWindow && msg->message == WM_DESTROY) {
            //  closing some window ... how do I know it's the main window?
            //  I just captured the top window when I was initialized...
            if (hDialog) {
              ::DestroyWindow(hDialog);
            }
          }
        }
        __except(EXCEPTION_EXECUTE_HANDLER) {
          ::OutputDebugString("NuXporter ::IsDialogMessage() crashed!\n");
          ::CloseWindow(hDialog);
          hDialog = 0;
          ::UnhookWindowsHookEx(hHook);
          hHook = 0;
        }
      }
      break;
    default:
      break;
  }
  return ret;
}

}//END EXTERN C


#if PROFILE

extern "C" {

struct FuncEntry {
  size_t    level0;
  size_t    level1;
  long long time;
};

struct StackEntry {
  FuncEntry *     func_;
  size_t          esp_;
};

#define NUMSLOTS 16384

struct ProfileRoot {
  size_t          index_;
  size_t          missing_;
  StackEntry      current_[256];        //  max stack depth
  FuncEntry       funcs_[NUMSLOTS+256];
  FuncEntry       temp_;
  DWORD           threadId_;
  ProfileRoot *   next_;
};

static ProfileRoot * gRoot;

static DWORD gTls;

#if FORCE_SINGLE_CPU
__declspec(naked) long long local_rdtsc()
{
  __asm {
    rdtsc
    ret
  }
}
#else
__declspec(naked) long long local_rdtsc()
{
  __asm {
    push ebp
    mov ebp, esp
    sub esp, 8
    lea eax, [ebp-8];
    push eax
    call dword ptr [QueryPerformanceCounter]
    pop eax
    pop edx
    pop ebp
    ret
  }
}
#endif

__declspec(naked) void _penter()
{
  __asm {
    push eax
    push ecx
    push edx
    push esi
    push edi
    push ebx
    push ebp
    mov ebp, esp
    sub esp, 36
  }
  size_t * stak;
  __asm {
    mov stak, ebp
  }
  if (gTls) {
    ProfileRoot * pr;
    pr = (ProfileRoot *)::TlsGetValue(gTls);
    if (pr) {
      size_t func = stak[7];
      size_t ret = stak[8];
      size_t hash = ((func >> 6) ^ (ret<<1)) & (NUMSLOTS-1);
      size_t cnt = 0;
      FuncEntry * fe = &pr->temp_;
      while (cnt < 256) {   //  only search forward 256 slots
        if (pr->funcs_[hash+cnt].level0 == func && pr->funcs_[hash+cnt].level1 == ret) {
          fe = &pr->funcs_[hash+cnt];
          break;
        }
        else if (pr->funcs_[hash+cnt].level0 == 0) {
          pr->funcs_[hash+cnt].level0 = func;
          pr->funcs_[hash+cnt].level1 = ret;
          fe = &pr->funcs_[hash+cnt];
          break;
        }
        else {
          ++cnt;
        }
      }
      if (fe == &pr->temp_) {
        pr->missing_++;
      }
      if (pr->index_ > 0 && pr->current_[pr->index_-1].esp_ <= (size_t)stak) {
        __asm {
          int 3;  //  this should not happen!
        }
      }
      pr->current_[pr->index_].func_ = fe;
      pr->current_[pr->index_].esp_ = (size_t)stak;
      pr->index_++;
      if (pr->index_ >= sizeof(pr->current_)/sizeof(pr->current_[0])) {
        __asm {
          int 3   //  Oops! Out of stack!
        }
      }
      fe->time -= local_rdtsc();
    }
  }
  __asm {
    add esp, 36
    pop ebp
    pop ebx
    pop edi
    pop esi
    pop edx
    pop ecx
    pop eax
    ret
  }
}

__declspec(naked) void _pexit()
{
  __asm {
    push eax
    push ecx
    push edx
    push esi
    push edi
    push ebx
    push ebp
    mov ebp, esp
    sub esp, 20
  }
  size_t stak;
  __asm {
    mov stak, ebp
  }
  if (gTls) {
    ProfileRoot * pr;
    pr = (ProfileRoot *)::TlsGetValue(gTls);
    //  deal with the case when an exception has unwould the stack
    if (pr) {
      if (pr->index_ > 0) {
again:
        pr->index_--;
        pr->current_[pr->index_].func_->time += local_rdtsc();
      }
      if (pr->index_ > 0 && pr->current_[pr->index_-1].esp_ <= stak) {  //  pop from exception
        goto again;
      }
    }
#if 0
    //  there's an initial condition where the loop above won't have run, 
    //  but pr is not NULL, and the esp_ value is 0; so allow that for 
    //  index_ == 0.
    if (pr != NULL && pr->current_[pr->index_].esp_ != stak &&
        !(pr->index_ == 0 && pr->current_[0].esp_ == 0)) {
      __asm {
        int 3   //  my secondary stack doesn't match!
      }
    }
#endif
  }
  __asm {
    add esp, 20
    pop ebp
    pop ebx
    pop edi
    pop esi
    pop edx
    pop ecx
    pop eax
    ret
  }
}

static void ThreadAttach()
{
#if FORCE_SINGLE_CPU
  DWORD_PTR b = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
  if (b == 0) {
    char buf[100];
    sprintf(buf, "SetThreadAffinityMask() failed: 0x%08x", ::GetLastError());
    ::MessageBox(0, buf, "Profiling", MB_OK);
  }
#endif
  ProfileRoot * pr;
  pr = (ProfileRoot *)::LocalAlloc(0, sizeof(ProfileRoot));
  memset(pr, 0, sizeof(*pr));
  pr->threadId_ = ::GetCurrentThreadId();
  //  hopefully, thread creation is serialized
  pr->next_ = gRoot;
  gRoot = pr;
  ::TlsSetValue(gTls, pr);
}

static void ThreadDetach()
{
}

static void ProcessAttach()
{
#if FORCE_SINGLE_CPU
  BOOL b = ::SetProcessAffinityMask(::GetCurrentProcess(), 1);
  if (b == FALSE) {
    ::MessageBox(0, "Could not SetProcessAffinityMask().", "Profiling", MB_OK);
  }
#endif
  gTls = ::TlsAlloc();
  ThreadAttach();
}

static void ProcessDetach()
{
  ::TlsFree(gTls);
  gTls = 0;
  // dump the profile data
  FILE * f = fopen("profile.pfx", "wb");
  fprintf(f, "<profile>\n");
  fprintf(f, "<module base='0x%lx' size='0x%lx'/>\n", (size_t)gModuleInformation.lpBaseOfDll,
      (size_t)gModuleInformation.SizeOfImage);
  while (gRoot) {
    ProfileRoot * d = gRoot;
    long long tps;
#if FORCE_SINGLE_CPU
    tps = 3200000000LL; //  assume a 3.2 GHz machine, which is bogus
#else
    ::QueryPerformanceFrequency((LARGE_INTEGER *)&tps);
#endif
    fprintf(f, "<thread id='%d' missing='%ld' tps='%lld' index='%d'>\n", 
        d->threadId_, d->missing_, tps, d->index_);
    for (size_t i = 0; i < sizeof(d->funcs_)/sizeof(d->funcs_[0]); ++i) {
      if (d->funcs_[i].level0) {
        fprintf(f, "<time func='0x%x' caller='0x%x' ticks='%lld'/>\n", 
            d->funcs_[i].level0, d->funcs_[i].level1, d->funcs_[i].time);
      }
    }
    fprintf(f, "</thread>\n");
    gRoot = gRoot->next_;
    ::LocalFree(d);
  }
  fprintf(f, "</profile>\n");
  fclose(f);
  gRoot = 0;
}

BOOL WINAPI DllMain(
  HANDLE hinstDLL, 
  DWORD dwReason, 
  LPVOID lpvReserved
)
{
  hModule = hinstDLL;
  switch (dwReason) {
    case DLL_PROCESS_ATTACH:
      ProcessAttach();
      break;
    case DLL_PROCESS_DETACH:
      ProcessDetach();
      break;
    case DLL_THREAD_ATTACH:
      ThreadAttach();
      break;
    case DLL_THREAD_DETACH:
      ThreadDetach();
      break;
    default:
      break;
  }
  return TRUE;
}

} //  extern "C"

#else // PROFILE

BOOL WINAPI DllMain(
  HANDLE hinstDLL, 
  DWORD dwReason, 
  LPVOID lpvReserved
)
{
  hModule = (HMODULE)hinstDLL;
  return TRUE;
}

#endif // PROFILE
